#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/uaccess.h>
#include <linux/fcntl.h>

#define KALLSYMS_LOOKUP_NAME    0xc015cf20
#define PAGE_SIZE               (1<<12)

//extern unsigned long kallsyms_lookup_name(const char *name);
static unsigned int patch_kernel_func(unsigned int handler, unsigned int old_func, unsigned int new_func);
static void hook_func(void);

/* clear WP bit of CR0, and return the original value */
unsigned int clear_and_return_cr0(void)
{
    unsigned int cr0 = 0;
    unsigned int ret;

    asm volatile("movl %%cr0, %%eax\n\t"
        : "=a"(cr0)
        );
    ret = cr0;

    /* clear the 16 bit of cr0, WP */
    cr0 &= 0xfffeffff;

    asm volatile ("movl %%eax, %%cr0\n\t"
        :
        : "a"(cr0)
        );
    
    return ret;
}

/* set cr0 with new value */
void setback_cr0(unsigned int val)
{
    asm volatile ("movl %%eax, %%cr0\n\t"
        :
        : "a"(val)
        );
}

static int hello_init(void)
{
    int fd, a;
    char *file = "file";
    asmlinkage ssize_t (*sys_read)(unsigned int fd, char *buf, size_t c);
    unsigned long (*kallsyms_lookup_name)(const char *name);
    void *vfs_read;
    
    kallsyms_lookup_name = KALLSYMS_LOOKUP_NAME;
    sys_read = (void *)kallsyms_lookup_name("sys_read");
    printk(KERN_ALERT "sys_read at %p\n", sys_read);
    vfs_read = (void *)kallsyms_lookup_name("vfs_read");
    printk(KERN_ALERT "vfs_read at %p\n", vfs_read);

    patch_kernel_func(sys_read, vfs_read, hook_func);

    printk(KERN_ALERT "Hello World!\n");

    /*fs = get_fs();
    set_fs(get_ds());
    fd = sys_open(file, O_RDONLY, 0);
    a = 0;
    sys_read(fd, &a, 4);
    set_fs(fs);*/
    return 0;
}

static void hook_func(void)
{
    asm volatile (
        "push %eax\n\t"
        "push %ecx\n\t"
        "push %edx\n\t"
        );

    asmlinkage ssize_t (*sys_read)(unsigned int fd, char *buf, size_t count);
    unsigned long (*kallsyms_lookup_name)(const char *name);
    void *vfs_read;

    kallsyms_lookup_name = KALLSYMS_LOOKUP_NAME;
    sys_read = (void *)kallsyms_lookup_name("sys_read");
    vfs_read = (void *)kallsyms_lookup_name("vfs_read");

    patch_kernel_func(sys_read, hook_func, vfs_read);

    printk(KERN_ALERT "hooked success!\n");

    asm volatile (
        "movl -0xc(%%ebp), %%ebx\n\t"
        "movl -0x8(%%ebp), %%esi\n\t"
        "pop %%edx\n\t"
        "pop %%ecx\n\t"
        "pop %%eax\n\t"
        "movl %%ebp, %%esp\n\t"
        "pop %%ebp\n\t"
        "jmp *%%edi\n\t"
        :
        : "D"(vfs_read)
        );
}

static unsigned int patch_kernel_func(unsigned int handler, unsigned int old_func,
    unsigned int new_func)
{
    unsigned int cr0;
    unsigned char *p = (unsigned char *)handler;
    unsigned char buf[4] = "\x00\x00\x00\x00";
    unsigned int offset = 0;
    unsigned int orig = 0;
    int i = 0;

    printk(KERN_ALERT "hook engine: start patch old_func at: 0x%08x\n",
        old_func);

    while (1)
    {
        if (i > 512)
            return 0;

        if (p[0] == 0xe8)
        {
            printk(KERN_ALERT "hook engine: found opcode 0x%02x\n",
                p[0]);

            printk(KERN_ALERT "hook engine: call ins at: 0x%08x\n",
                (unsigned int)p);

            buf[0] = p[1];
            buf[1] = p[2];
            buf[2] = p[3];
            buf[3] = p[4];
        
            printk(KERN_ALERT "hook engine: 0x%02x 0x%02x 0x%02x 0x%02x\n",
                p[1], p[2], p[3], p[4]);

            offset = *(unsigned int *)buf;
            printk(KERN_ALERT "hook engine: offset: 0x%08x\n", offset);

            orig = offset + (unsigned int)p + 5;
            printk(KERN_ALERT "hook engine: original func: 0x%08x\n", orig);

            if (orig == old_func)
            {
                printk(KERN_ALERT "hook engine: found old func: 0x%0x\n", 
                    old_func);
                printk("inc index %d\n", i);
                break;
            }
        }
        p ++;
        i ++;
    }

    offset = new_func - (unsigned int)p - 5;
    printk(KERN_ALERT "hook engine: new func offset: 0x%08x\n", offset);
    
    cr0 = clear_and_return_cr0();

    p[1] = (offset & 0x000000ff);
    p[2] = (offset & 0x0000ff00) >> 8;
    p[3] = (offset & 0x00ff0000) >> 16;
    p[4] = (offset & 0xff000000) >> 24;

    setback_cr0(cr0);

    printk(KERN_ALERT "hook engine: patched new func offset.\n");

    return orig;
}

static void hello_exit(void)
{
    printk(KERN_ALERT "Bye World!\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("xuzhi");
